#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'cuihailong'

import logging, os, time, json, asyncio, dbsql
from datetime import datetime
from aiohttp import web
from jinja2 import Environment, FileSystemLoader
from coroweb import add_static, add_routes, get
from handlers import cookie2user
COOKIE_NAME = 'aiolongsession'
_COOKIE_KEY = 'aiolong'
logging.basicConfig(level=logging.INFO)  # 指定日志级别为info


def init_jinja2(app, **kw):
    # 初始化jinja2模板
    logging.info('init jinja2...')
    options = dict(
        autoescape = kw.get('autoescape', True),
        block_start_string = kw.get('block_start_string', '{%'),
        block_end_string = kw.get('block_end_string', '%}'),
        variable_start_string = kw.get('variable_start_string', '{{'),
        variable_end_string = kw.get('variable_end_string', '}}'),
        auto_reload = kw.get('auto_reload', True)
    )
    path = kw.get('path', None)
    if path is None:
        path = os.path.join(os.path.abspath(os.path.dirname(os.path.dirname(__file__))), r'staticfiles\templates')
    logging.info('set jinja2 template path: %s' % path)
    # FileSystemLoader(path) 指定模板路径
    env = Environment(loader=FileSystemLoader(path), **options)  # 定义jinja2环境
    filters = kw.get('filters', None)
    if filters is not None:
        # Jinja2 的默认 filter 是一个字典，查看方式：Environment.filters
        for name, f in filters.items():
            env.filters[name] = f
    app['__templating__'] = env


async def auth_factory(app, handler):
    """
    中间件认证模块
    :param app:
    :param handler:
    :return:
    """
    async def auth(request):
        logging.info('check user: %s %s' % (request.method, request.path))
        request.__user__ = None
        cookie_str = request.cookies.get(COOKIE_NAME)
        if cookie_str:
            user = await cookie2user(cookie_str)
            if user:
                logging.info('set current user: %s' % user.email)
                request.__user__ = user
        if request.path.startswith('/manage/') and (request.__user__ is None or not request.__user__.admin):
            return web.HTTPFound('/signin')
        return await handler(request)
    return auth


async def logger_factory(app, handler):
    """
    记录请求方法以及路径的日志
    :param app:
    :param handler:
    :return:
    """
    async def logger(request):
        logging.info('Request: %s %s' % (request.method, request.path))
        return await handler(request)
    return logger


async def data_factory(app, handler):
    """
    处理请求的数据
    :param app:
    :param handler:
    :return:
    """
    async def parse_data(request):
        if request.method == 'POST':
            if request.content_type.startswith('application/json'):
                request.__data__ = await request.json()
                logging.info('request json: %s' % str(request.__data__))
            elif request.content_type.startswith('application/x-www-form-urlencoded'):
                request.__data__ = await request.post()
                logging.info('request form: %s' % str(request.__data__))
        return await handler(request)
    return parse_data


async def response_factory(app, handler):
    """
    处理响应并封装成合理的response，返回
    :param app:
    :param handler:
    :return:
    """
    async def response(request):
        logging.info('Response handler...')
        r = await handler(request)
        if isinstance(r, web.StreamResponse):
            return r
        if isinstance(r, bytes):
            resp = web.Response(body=r)
            resp.content_type = 'application/octet-stream'
            return resp
        if isinstance(r, str):
            if r.startswith('redirect:'):
                return web.HTTPFound(r[9:])
            resp = web.Response(body=r.encode('utf-8'))
            resp.content_type = 'text/html;charset=utf-8'
            return resp
        if isinstance(r, dict):
            template = r.get('__template__')
            if template is None:
                resp = web.Response(body=json.dumps(r, ensure_ascii=False, default=lambda o: o.__dict__).encode('utf-8'))
                resp.content_type = 'application/json;charset=utf-8'
                return resp
            else:
                r['__user__'] = request.__user__
                resp = web.Response(body=app['__templating__'].get_template(template).render(**r).encode('utf-8'))
                resp.content_type = 'text/html;charset=utf-8'
                return resp
        if isinstance(r, int) and 100 <= r < 600:
            return web.Response(r)
        if isinstance(r, tuple) and len(r) == 2:
            t, m = r
            if isinstance(t, int) and 100 <= t < 600:
                return web.Response(t, str(m))
        # default:
        resp = web.Response(body=str(r).encode('utf-8'))
        resp.content_type = 'text/plain;charset=utf-8'
        return resp
    return response


def datetime_filter(t):
    """
    时间展示
    :param t:
    :return:
    """
    delta = int(time.time() - t)
    if delta < 60:
        return u'1分钟前'
    if delta < 3600:
        return u'%s分钟前' % (delta // 60)
    if delta < 86400:
        return u'%s小时前' % (delta // 3600)
    if delta < 604800:
        return u'%s天前' % (delta // 86400)
    dt = datetime.fromtimestamp(t)
    return u'%s年%s月%s日' % (dt.year, dt.month, dt.day)


async def init(loop_info):
    """
    初始化连接
    :param loop_info:
    :return:
    """
    await dbsql.create_pool(loop=loop_info, host='localhost', port=3306, user='root', password='root', db='aiopylong')
    app = web.Application(middlewares=[logger_factory, response_factory, auth_factory])
    init_jinja2(app, filters=dict(datetime=datetime_filter))
    add_routes(app, 'handlers')   # 将路由添加到内存中，等待访问
    add_static(app)  # 添加静态文件
    apprunner = web.AppRunner(app)  # 构造AppRunner对象
    await apprunner.setup()  # 调用setup()方法，注意因为源码中这个方法被async修饰，所以前面要加上await，否则报错
    srv = await loop.create_server(apprunner.server, "127.0.0.1", 9000)  # 创建server（TCP）
    logging.info('server started at http://127.0.0.1:9000...')
    return srv


loop = asyncio.get_event_loop()
"""
    1.检查在调用函数时是否有循环运行
    2.返回其 pid 与当前进程 pid 匹配的运行循环（如果有）
    3.如果没有，获取存储在 asynci omodule 中的全局变量中的线程全局 LoopPolicy 实例。
"""
loop.run_until_complete(init(loop))  # 开始执行事件循环的第一个函数
loop.run_forever()  # 启动事件循环
